<?php
/**
 * @package PHPKit.
 * @author: mawenpei
 * @date: 2016/6/24
 * @time: 11:06
 */
namespace HuoCore\Helper;

class BrowserAgent
{
    protected $userAgentString;
    protected $browserName;
    protected $browserVersion;
    protected $operatingSystem;
    protected $engine;

    public function __construct($userAgentString = null, phpUserAgentStringParser $userAgentStringParser = null)
    {
        $this->configureFromUserAgentString($userAgentString, $userAgentStringParser);
    }

    /**
     * Get the browser name
     *
     * @return string the browser name
     */
    public function getBrowserName()
    {
        return $this->browserName;
    }

    /**
     * Set the browser name
     *
     * @param   string  $name the browser name
     */
    public function setBrowserName($name)
    {
        $this->browserName = $name;
    }

    /**
     * Get the browser version
     *
     * @return string the browser version
     */
    public function getBrowserVersion()
    {
        return $this->browserVersion;
    }

    /**
     * Set the browser version
     *
     * @param   string  $version the browser version
     */
    public function setBrowserVersion($version)
    {
        $this->browserVersion = $version;
    }

    /**
     * Get the operating system name
     *
     * @return  string the operating system name
     */
    public function getOperatingSystem()
    {
        return $this->operatingSystem;
    }

    /**
     * Set the operating system name
     *
     * @param   string $operatingSystem the operating system name
     */
    public function setOperatingSystem($operatingSystem)
    {
        $this->operatingSystem = $operatingSystem;
    }

    /**
     * Get the engine name
     *
     * @return  string the engine name
     */
    public function getEngine()
    {
        return $this->engine;
    }

    /**
     * Set the engine name
     *
     * @param $engine  string $operatingSystem the engine name
     */
    public function setEngine($engine)
    {
        $this->engine = $engine;
    }

    /**
     * Get the user agent string
     *
     * @return  string the user agent string
     */
    public function getUserAgentString()
    {
        return $this->userAgentString;
    }

    /**
     * Set the user agent string
     *
     * @param   string $userAgentString the user agent string
     */
    public function setUserAgentString($userAgentString)
    {
        $this->userAgentString = $userAgentString;
    }

    /**
     * Tell whether this user agent is unknown or not
     *
     * @return boolean  true if this user agent is unknown, false otherwise
     */
    public function isUnknown()
    {
        return empty($this->browserName);
    }

    /**
     * @return string combined browser name and version
     */
    public function getFullName()
    {
        return $this->getBrowserName().' '.$this->getBrowserVersion();
    }

    public function __toString()
    {
        return $this->getFullName();
    }

    /**
     * Configure the user agent from a user agent string
     * @param   string                    $userAgentString        the user agent string
     * @param   phpUserAgentStringParser  $userAgentStringParser  the parser used to parse the string
     */
    public function configureFromUserAgentString($userAgentString, phpUserAgentStringParser $userAgentStringParser = null)
    {
        if(null === $userAgentStringParser)
        {
            $userAgentStringParser = new phpUserAgentStringParser();
        }

        $this->setUserAgentString($userAgentString);

        $this->fromArray($userAgentStringParser->parse($userAgentString));
    }

    /**
     * Convert the user agent to a data array
     *
     * @return  array data
     */
    public function toArray()
    {
        return array(
            'browser_name'      => $this->getBrowserName(),
            'browser_version'   => $this->getBrowserVersion(),
            'operating_system'  => $this->getOperatingSystem()
        );
    }

    /**
     * Configure the user agent from a data array
     *
     * @param array $data
     */
    public function fromArray(array $data)
    {
        $this->setBrowserName($data['browser_name']);
        $this->setBrowserVersion($data['browser_version']);
        $this->setOperatingSystem($data['operating_system']);
        $this->setEngine($data['engine']);
    }
}

class phpUserAgentStringParser
{
    /**
     * Parse a user agent string.
     *
     * @param   string  $userAgentString  defaults to $_SERVER['HTTP_USER_AGENT'] if empty
     * @return  array   (                 the user agent informations
     *            'browser_name'      => 'firefox',
     *            'browser_version'   => '3.6',
     *            'operating_system'  => 'linux'
     *          )
     */
    public function parse($userAgentString = null)
    {
        // use current user agent string as default
        if(!$userAgentString)
        {
            $userAgentString = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
        }

        // parse quickly (with medium accuracy)
        $informations = $this->doParse($userAgentString);


        // run some filters to increase accuracy
        foreach($this->getFilters() as $filter)
        {
            $this->$filter($informations);
        }

        return $informations;
    }

    /**
     * Detect quickly informations from the user agent string
     *
     * @param   string $userAgentString   user agent string
     * @return  array                     user agent informations array
     */
    protected function doParse($userAgentString)
    {
        $userAgent = array(
            'string'            => $this->cleanUserAgentString($userAgentString),
            'browser_name'      => null,
            'browser_version'   => null,
            'operating_system'  => null,
            'engine'            => null
        );

        if(empty($userAgent['string']))
        {
            return $userAgent;
        }

        // build regex that matches phrases for known browsers
        // (e.g. "Firefox/2.0" or "MSIE 6.0" (This only matches the major and minor
        // version numbers.  E.g. "2.0.0.6" is parsed as simply "2.0"
        $pattern = '#('.join('|', $this->getKnownBrowsers()).')[/ ]+([0-9]+(?:\.[0-9]+)?)#';

        // Find all phrases (or return empty array if none found)
        if (preg_match_all($pattern, $userAgent['string'], $matches))
        {
            // Since some UAs have more than one phrase (e.g Firefox has a Gecko phrase,
            // Opera 7,8 have a MSIE phrase), use the last one found (the right-most one
            // in the UA).  That's usually the most correct.
            $i = count($matches[1])-1;

            if (isset($matches[1][$i]))
            {
                $userAgent['browser_name'] = $matches[1][$i];
            }
            if (isset($matches[2][$i]))
            {
                $userAgent['browser_version'] = $matches[2][$i];
            }
        }

        // Find operating system
        $pattern = '#'.join('|', $this->getKnownOperatingSystems()).'#';

        if (preg_match($pattern, $userAgent['string'], $match))
        {
            if (isset($match[0]))
            {
                $userAgent['operating_system'] = $match[0];
            }
        }

        // Find engine
        $pattern = '#'.join('|', $this->getKnownEngines()).'#';

        if (preg_match($pattern, $userAgent['string'], $match))
        {
            if (isset($match[0]))
            {
                $userAgent['engine'] = $match[0];
            }
        }

        return $userAgent;
    }

    /**
     * Make user agent string lowercase, and replace browser aliases
     *
     * @param   string $userAgentString the dirty user agent string
     * @return  string                  the clean user agent string
     */
    public function cleanUserAgentString($userAgentString)
    {
        // clean up the string
        $userAgentString = trim(strtolower($userAgentString));

        // replace browser names with their aliases
        $userAgentString = strtr($userAgentString, $this->getKnownBrowserAliases());

        // replace operating system names with their aliases
        $userAgentString = strtr($userAgentString, $this->getKnownOperatingSystemAliases());

        // replace engine names with their aliases
        $userAgentString = strtr($userAgentString, $this->getKnownEngineAliases());

        return $userAgentString;
    }

    /**
     * Get the list of filters that get called when parsing a user agent
     *
     * @return  array list of valid callables
     */
    public function getFilters()
    {
        return array(
            'filterAndroid',
            'filterGoogleChrome',
            'filterSafariVersion',
            'filterOperaVersion',
            'filterYahoo',
            'filterMsie',
        );
    }

    /**
     * Add a filter to be called when parsing a user agent
     *
     * @param   string $filter name of the filter method
     */
    public function addFilter($filter)
    {
        $this->filters += $filter;
    }

    /**
     * Get known browsers
     *
     * @return  array the browsers
     */
    protected function getKnownBrowsers()
    {
        return array(
            'msie',
            'firefox',
            'safari',
            'webkit',
            'opera',
            'netscape',
            'konqueror',
            'gecko',
            'chrome',
            'googlebot',
            'iphone',
            'msnbot',
            'applewebkit'
        );
    }

    /**
     * Get known browser aliases
     *
     * @return  array the browser aliases
     */
    protected function getKnownBrowserAliases()
    {
        return array(
            'shiretoko'     => 'firefox',
            'namoroka'      => 'firefox',
            'shredder'      => 'firefox',
            'minefield'     => 'firefox',
            'granparadiso'  => 'firefox'
        );
    }

    /**
     * Get known operating system
     *
     * @return  array the operating systems
     */
    protected function getKnownOperatingSystems()
    {
        return array(
            'windows',
            'macintosh',
            'linux',
            'freebsd',
            'unix',
            'iphone'
        );
    }

    /**
     * Get known operating system aliases
     *
     * @return  array the operating system aliases
     */
    protected function getKnownOperatingSystemAliases()
    {
        return array();
    }

    /**
     * Get known engines
     *
     * @return  array the engines
     */
    protected function getKnownEngines()
    {
        return array(
            'gecko',
            'webkit',
            'trident',
            'presto'
        );
    }

    /**
     * Get known engines aliases
     *
     * @return  array the engines aliases
     */
    protected function getKnownEngineAliases()
    {
        return array();
    }

    /**
     * Filters
     */

    /**
     * Google chrome has a safari like signature
     */
    protected function filterGoogleChrome(array &$userAgent)
    {
        if ('safari' === $userAgent['browser_name'] && strpos($userAgent['string'], 'chrome/'))
        {
            $userAgent['browser_name'] = 'chrome';
            $userAgent['browser_version'] = preg_replace('|.+chrome/([0-9]+(?:\.[0-9]+)?).+|', '$1', $userAgent['string']);
        }
    }

    /**
     * Safari version is not encoded "normally"
     */
    protected function filterSafariVersion(array &$userAgent)
    {
        if ('safari' === $userAgent['browser_name'] && strpos($userAgent['string'], ' version/'))
        {
            $userAgent['browser_version'] = preg_replace('|.+\sversion/([0-9]+(?:\.[0-9]+)?).+|', '$1', $userAgent['string']);
        }
    }

    /**
     * Opera 10.00 (and higher) version number is located at the end
     */
    protected function filterOperaVersion(array &$userAgent)
    {
        if('opera' === $userAgent['browser_name'] && strpos($userAgent['string'], ' version/'))
        {
            $userAgent['browser_version'] = preg_replace('|.+\sversion/([0-9]+\.[0-9]+)\s*.*|', '$1', $userAgent['string']);
        }
    }

    /**
     * Yahoo bot has a special user agent string
     */
    protected function filterYahoo(array &$userAgent)
    {
        if (null === $userAgent['browser_name'] && strpos($userAgent['string'], 'yahoo! slurp'))
        {
            $userAgent['browser_name'] = 'yahoobot';
        }
    }

    /**
     * MSIE does not always declare its engine
     */
    protected function filterMsie(array &$userAgent)
    {
        if ('msie' === $userAgent['browser_name'] && empty($userAgent['engine']))
        {
            $userAgent['engine'] = 'trident';
        }
    }

    /**
     * Android has a safari like signature
     */
    protected function filterAndroid(array &$userAgent) {
        if ('safari' === $userAgent['browser_name'] && strpos($userAgent['string'], 'android ')) {
            $userAgent['browser_name'] = 'android';
            $userAgent['operating_system'] = 'android';
            $userAgent['browser_version'] = preg_replace('|.+android ([0-9]+(?:\.[0-9]+)+).+|', '$1', $userAgent['string']);
        }
    }
}